require 'singleton'

class Point
	attr_reader :x, :y
	attr_writer :x, :y
	
	def initialize x, y
		@x = x
		@y = y
	end
	
	def manhattan point
		(point.x - @x) ** 2 + (point.y - @y) ** 2
	end
end


class RCase
	include Singleton
end

class CaseVide < RCase
end

class CaseTrou < RCase
end

class CaseVerte < RCase
end

class CaseRouge < RCase
end


class Grille
	attr_reader :width, :height, :grille
	attr_writer :width, :height, :grille
	
	def initialize width, height
		@width = width
		@height = height
		
		@grille = Array.new
		
		@height.downto(1) do
			@grille << Array.new
		end
	end
	
	@@chartocase = {
		'o' => CaseTrou.instance,
		'-' => CaseVide.instance,
		'r' => CaseRouge.instance,
		'v' => CaseVerte.instance
		}
	
	def self.from_s str
		lines = str.split "\n"
		grille = self.new(lines[0].length, lines.length)
		
		lines.each_with_index do |line, y|
			line.split(//).each_with_index do |car, x|
				point = Point.new x, y
				grille[point] = @@chartocase[car]
			end
		end
		
		return grille
	end
	
	
	@@casetochar = Hash.new
	@@chartocase.each { |k, v| @@casetochar[v] = k }
	
	def to_s
		str = ''
		@grille.each do |line|
			line.each do |rcase|
				str << @@casetochar[rcase]
			end
			str << "\n"
		end
		
		return str
	end
	
	def [] point
		@grille[point.y][point.x]
	end
	
	def []= point, value
		@grille[point.y][point.x] = value
	end
	
	def eachCase
		@grille.each_with_index do |line, y|
			line.each_with_index do |rcase, x|
				yield rcase, Position.new(x, y)
			end
		end
	end
	
	def isValid? point
		point.x >= 0 and point.x < @width and point.y >= 0 and point.y < @height
	end
	
	def eachDirections dirs, point
		dirs.each do |dir|
			dest = Point.new(point.x + dir.x, point.y + dir.y)
			if isValid? dest then
				yield dest
			end
		end
	end
	
	@@around9 = [Point.new(-1, -1), Point.new(-1, 0), Point.new(-1, 1),
		Point.new(0, -1), Point.new(0, 1),
		Point.new(1, -1), Point.new(1, 0), Point.new(1, 1)]
	
	def eachAround point
		eachDirections(@@around9, point) do |point|
			yield self[point], point
		end
	end
	
	@@jump4 = [Point.new(0, -2), Point.new(0, 2),
		Point.new(-2, 0), Point.new(2, 0)]
	
	def eachJump point
		eachDirections(@@jump4, point) do |point|
			yield self[point], point
		end
	end
end

class BacteriaGrille < Grille
	#~ attr_reader :joueur
	#~ attr_writer :joueur
	
	def jouerCoup coup, joueur
		if (coup.dest.manhattan(coup.orig) <= 4 and self[coup.orig].is_a? joueur.class \
			and self[coup.dest].is_a?(CaseVide) ) then
			
			if (coup.type.is_a? Jump) then
				self[coup.orig] = CaseVide.instance
			end
			
			self[coup.dest] = joueur.class.instance
			
			eachAround(Point.new(coup.dest.x, coup.dest.y)) do |rcase, point|
				if (rcase.is_a? CaseRouge or rcase.is_a? CaseVerte ) then
					self[point] = joueur.class.instance
				end
			end
		else
			throw "coup non valide"
		end
	end
	
	def eachCoup joueur, orig
		rcase = self[orig]
		#~ eachCase do |rcase, orig|
			if joueur.is_a?(rcase.class) then
				eachAround orig do |rcase, dest|
					yield Coup.new(orig, dest) if rcase.is_a?(CaseVide)
				end
				
				eachJump orig do |rcase, dest|
					yield Coup.new(orig, dest) if rcase.is_a?(CaseVide)
				end
			end
		#~ end
	end
end

class Position < Point
end

class TypeCoup
	include Singleton
end

class Jump < TypeCoup
end

class Mitose < TypeCoup
end

class Coup
	attr_reader :orig, :dest, :type
	attr_writer :orig, :dest, :type
	
	def initialize *args
		if args[0].is_a?(Point) then
			@orig, @dest = args[0..1]
			@type = args[2]
		end
		
		if args[0].is_a? Integer then
			@orig = Point.new(args[0], args[1])
			@dest = Point.new(args[2], args[3])
			@type = args[4]
		end
		
		if @orig.manhattan(@dest) == 4 then
			@type = Jump.instance
		elsif @orig.manhattan(@dest) <= 2 then
			@type = Mitose.instance
		else
			throw "coup non valide"
		end
	end
end
